/**
 * Copyright @ 2023 iAuto (Shanghai) Co., Ltd.
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are NOT permitted except as agreed by
 * iAuto (Shanghai) Co., Ltd.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 */

// import jsonblocks from '@/lib/db/scenarioWithNoOp.json';

import React, { useCallback, useEffect, useState } from 'react';
import { connect } from 'dva';
import VMScratchBlocks, { BLOCKS_DEFAULT_OPTIONS } from '@/lib/blocks-vm';
import { getCategory, getBlocks, IBlockDataType } from '@/lib/custom-extension';
import projectData from '@/lib/default-project/project-data';
import defineDynamicBlock from '@/lib/define-dynamic-block';
import log from '@/lib/log.js';
import makeToolboxXML from '@/lib/make-toolbox-xml';
import CompositeComponent, { ICompositeComponent } from '../CompositeComponent';
import { useSetState } from 'ahooks';
import ArgumentTypes from '@scene/scene-vm/src/extension-support/argument-type';

// import { scenarioDetail } from '@/services/httpService';

import './index.less';
import { useMount, useUnmount } from 'ahooks';
import { isObject } from 'lodash';
import {
  checkPlaceholdersAndParams,
  checkSenceJson,
  compareVals,
  illegalDataSearch,
  replaceDateCustomData,
  setBlockValue,
  setUnFoucs,
  useModal,
} from '@/utils';
// import { sceneDetail } from '@/services/httpService';
import template from '@/lib/default-project/template.json';
import { message } from 'antd';
import { ElementDype, ModalList } from '@/lib/dataType';
import router from 'umi/router';

interface BlocksProps {
  vm: any;
  id?: string;
  blockRef?: any;
  userId?: string;
  dispatch: any;
  location: any;
  editGlobal: any;
}

interface KeysItem {
  id: string;
  name: string;
  blocks: any;
  menus: any;
}

type Keys = KeysItem[];

const mapStateToProps = (state: any) => ({
  editGlobal: state.editGlobal,
});

let workspace: any = null;
let ScratchBlocks: any = null;

const Blocks: React.FC<BlocksProps> = ({ dispatch, ...props }) => {
  const { vm, id, blockRef, location } = props;
  const [loadScene, setLoadScene] = useState(false);

  const { set: setGlobalState } = useModal(dispatch, 'editGlobal');

  const { skillJson, markBlocksMap, skillMap } = props.editGlobal;
  /* eslint-disable @typescript-eslint/no-empty-function */
  const [compositeState, setCompositeState] = useSetState<ICompositeComponent>({});

  // 处理扩展数据
  const getExtensions = () => {
    const keys: Keys = [];
    const categorys = getCategory(skillJson);

    const categoryKeys = Object.keys(categorys);
    vm.runtime.attachCustomKey('categoryKeys', categoryKeys); // 存扩展keys

    const markBlockIds = new Map<string, number[]>();

    categoryKeys.forEach((item) => {
      const { data, zhName, menuIconURI, blockIconURI } = categorys[item];
      const { blocks, menus, sort } = getBlocks(data, markBlockIds);
      // https://github.com/LLK/scratch-vm/blob/develop/docs/extensions.md
      const target = {
        blocks,
        menus,
        id: item,
        name: zhName,
        menuIconURI, // menu icon
        blockIconURI, // blocks icon // 统一添加前置icon
        // suffixIconURI: suffixIconURI // 统一添加后缀icon
        sort, // 排序
      };
      keys.push(target);
    });

    // 设置数据
    setGlobalState('markBlocksMap', markBlockIds);

    // 排序扩展 正向排序
    return keys.slice().sort((a: any, b: any) => {
      try {
        const [aSort] = a.sort.split('.');
        const [bSort] = b.sort.split('.');
        return compareVals(Number(aSort), Number(bSort));
      } catch (error) {
        return 0;
      }
    });
  };

  const automaticImportComponent = ({ id: key }: KeysItem) => {
    // 判断是否加载了扩展
    if (!vm.extensionManager.isExtensionLoaded(key)) {
      vm.extensionManager.loadExtensionURL(key).then(() => { });
    }
  };

  // 加载数据方法
  const loadDefaultProject = async (blocks: any) => {
    const markIds = (markBlocksMap as Map<string, number[]>).get('markIds') || [];
    const checkRes = checkSenceJson(blocks, markIds);
    const { sceneUuid } = location.query;
    if (checkRes !== null) {
      dispatch({
        type: 'editGlobal/setGlobalState',
        payload: {
          projectChanged: false,
        },
      });
      dispatch({
        type: 'editGlobal/setModalConfirm',
        payload: {
          msg: <p>{`场景中存在部分车型无法使用的能力`}<br/>{`能力ID：${checkRes}`}</p>,
          okCb: ()=>{
            router.replace('/soaManage/scene/official/detail?sceneUuid=' + sceneUuid);
          }
        },
      });
      return;
    }

    return new Promise((resolve, reject) => {
      if (!isObject(blocks)) {
        reject(false);
        return;
      }
      vm.loadProject(JSON.stringify(projectData(blocks)))
        .then(() => { })
        .catch((error: any) => {
          log.warn(error);
          reject(false);
        })
        .then(() => {
          resolve(true);
          dispatch({
            type: 'editGlobal/setGlobalState',
            payload: {
              projectChanged: false,
            },
          });
        });
    });
  };

  // 获取当前xml 函数
  const getToolboxXML = useCallback(() => {
    // Use try/catch because this requires digging pretty deep into the VM
    // Code inside intentionally ignores several error situations (no stage, etc.)
    // Because they would get caught by this try/catch
    try {
      let { editingTarget: target } = vm;
      const stage = vm.runtime.getTargetForStage();
      if (!target) target = stage; // If no editingTarget, use the stage

      const stageCostumes = stage.getCostumes();
      const targetCostumes = target.getCostumes();
      const targetSounds = target.getSounds();
      const dynamicBlocksXML = vm.runtime.getBlocksXML(target);
      return makeToolboxXML(
        false,
        target.isStage,
        target.id,
        dynamicBlocksXML,
        targetCostumes[targetCostumes.length - 1].name,
        stageCostumes[stageCostumes.length - 1].name,
        targetSounds.length > 0 ? targetSounds[targetSounds.length - 1].name : '',
      );
    } catch {
      return null;
    }
  }, [vm]);

  // workspace 更新
  const onWorkspaceUpdate = useCallback(
    (data: { xml: string }) => {
      // When we change sprites, update the toolbox to have the new sprite's blocks
      const toolboxXML = getToolboxXML();
      workspace?.updateToolbox(toolboxXML);
      const dom = ScratchBlocks.Xml.textToDom(data.xml);
      ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml(dom, workspace);
    },
    [workspace, ScratchBlocks, getToolboxXML],
  );

  // 扩展数据 回调处理 挂载到 ScratchBlocks.Blocks上
  const handleExtensionAdded = useCallback(
    (categoryInfo: any) => {
      const defineBlocks = (blockInfoArray: any) => {
        if (blockInfoArray && blockInfoArray.length > 0) {
          const staticBlocksJson: any[] = [];
          const dynamicBlocksInfo: any[] = [];
          blockInfoArray.forEach((blockInfo: any) => {
            if (blockInfo.info && blockInfo.info.isDynamic) {
              dynamicBlocksInfo.push(blockInfo);
            } else if (blockInfo.json) {
              staticBlocksJson.push(blockInfo.json);
            }
            // otherwise it's a non-block entry such as '---'
          });

          ScratchBlocks.defineBlocksWithJsonArray(staticBlocksJson);
          dynamicBlocksInfo.forEach((blockInfo) => {
            // This is creating the block factory / constructor -- NOT a specific instance of the block.
            // The factory should only know static info about the block: the category info and the opcode.
            // Anything else will be picked up from the XML attached to the block instance.
            const extendedOpcode = `${categoryInfo.id}_${blockInfo.info.opcode}`;
            const blockDefinition = defineDynamicBlock(
              ScratchBlocks,
              categoryInfo,
              blockInfo,
              extendedOpcode,
            );
            ScratchBlocks.Blocks[extendedOpcode] = blockDefinition;
          });
        }
      };

      // @iauto/scene-blocks implements a menu or custom field as a special kind of block ("shadow" block)
      // these actually define blocks and MUST run regardless of the UI state
      defineBlocks(
        Object.getOwnPropertyNames(categoryInfo.customFieldTypes).map(
          (fieldTypeName) => categoryInfo.customFieldTypes[fieldTypeName].scratchBlocksDefinition,
        ),
      );
      defineBlocks(categoryInfo.menus);
      defineBlocks(categoryInfo.blocks);

      try {
        // 限制大小
        const mathNumberLimits: any[] = [];
        // 自定义name
        const textLimits: any[] = [];

        categoryInfo.blocks.forEach((block) => {
          const extensionId = categoryInfo.id;
          block.json?.args0?.forEach((arg) => {
            const argInfo = block?.info?.arguments?.[arg.name];
            if (argInfo) {
              if (!argInfo.menu && argInfo.type === ArgumentTypes.STRING) {
                textLimits.push({
                  json: {
                    message0: '%1',
                    args0: [
                      {
                        type: 'field_input',
                        name: 'TEXT',
                        paramName: arg.name,
                      },
                    ],
                    output: 'String',
                    outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND,
                    colour: ScratchBlocks.Colours.textField,
                    colourSecondary: ScratchBlocks.Colours.textField,
                    colourTertiary: ScratchBlocks.Colours.textField,
                    type: `${extensionId}_text_${arg.name}`,
                  },
                });
              }

              if (!argInfo.menu && argInfo.type === ArgumentTypes.NUMBER) {
                mathNumberLimits.push({
                  json: {
                    message0: '%1',
                    args0: [
                      {
                        type: 'field_number',
                        name: 'NUM',
                        value: '0',
                        min: arg.min,
                        max: arg.max,
                        paramName: arg.name,
                      },
                    ],
                    output: 'Number',
                    outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND,
                    colour: ScratchBlocks.Colours.textField,
                    colourSecondary: ScratchBlocks.Colours.textField,
                    colourTertiary: ScratchBlocks.Colours.textField,
                    type: `${extensionId}_math_number_${arg.name}`,
                    limit: true,
                  },
                });
              }
            }
          });
        });

        defineBlocks(mathNumberLimits);
        defineBlocks(textLimits);
      } catch (error) {
        console.log('🚀 ~ file: index.tsx ~ line 336 ~ error', error);
      }
    },
    [ScratchBlocks],
  );

  // 项目更改回调
  const handleProjectChanged = useCallback(() => {
    dispatch({
      type: 'editGlobal/setGlobalState',
      payload: {
        projectChanged: true,
      },
    });
  }, [dispatch]);

  // 取消函数
  const handleReset = () => {
    setUnFoucs();
    setCompositeState({
      value: '',
      visible: false,
      onOk: () => { },
      onCancel: () => { },
    });
  };

  // 时间积木 颜色积木
  const extendFunc = (e: any) => {
    if (e?.element == 'field' && isObject(e?.targetBlock_?.dataType_)) {
      const dataType_ = e?.targetBlock_?.dataType_;
      // 当前积木的id
      const id = e?.targetBlock_?.id;
      const paramName = e?.block?.paramName || e?.block?.name;
      // console.log('🚀 ~ file: index.tsx ~ line 370 ~ extendFunc ~ e', e);

      const dataType: IBlockDataType = dataType_[paramName];
      // console.log('🚀 ~ file: index.tsx ~ line 367 ~ extendFunc ~ dataType', dataType);
      if (dataType === undefined) {
        return;
      }

      if (!ModalList.includes(dataType.elementType as string)) {
        return;
      }

      const value = e?.block?.getText() || e?.block?.getValue() || 0;

      setCompositeState({
        value: value,
        visible: true,
        dataType: dataType,
        id,
        onOk(value) {
          setBlockValue(e, value);
          handleReset();
        },
        onCancel: handleReset,
      });
    }
  };

  // 读取原始数据
  const readProjectJson = useCallback(() => {
    const json = JSON.parse(vm.toJSON());
    const { length } = json.targets;
    if (length) {
      return json.targets[length - 1].blocks;
    } else {
      log.warn('获取json targets异常');
      return {};
    }
  }, [vm]);

  // 数据判定
  const dataJudgment = () => {
    // 获取所有数据
    const projectJson = readProjectJson();
    // 找出非法数据
    const illegalData = illegalDataSearch(projectJson);
    // opacity 切换
    const blockDB_ = Blockly.getMainWorkspace().blockDB_;
    for (const blockKey in blockDB_) {
      const block = blockDB_[blockKey];
      if (illegalData.includes(blockKey)) {
        block.svgGroup_.style.opacity = 0.5;
      } else {
        block.svgGroup_.style.opacity = 1;
      }
    }
  };

  // 添加事件
  const attachVM = useCallback(() => {
    workspace.addChangeListener((e: any) => {
      vm.blockListener(e);
      // 当前积木扩展类型
      if (e.type === 'move' || e.type === 'delete') {
        dataJudgment();
      }

      // 扩展函数
      extendFunc(e);
    });

    const flyoutWorkspace = workspace.getFlyout().getWorkspace();
    flyoutWorkspace.addChangeListener(vm.flyoutBlockListener);
    flyoutWorkspace.addChangeListener(vm.monitorBlockListener);
    flyoutWorkspace.addChangeListener(extendFunc);

    vm.addListener('workspaceUpdate', onWorkspaceUpdate);
    vm.addListener('EXTENSION_ADDED', handleExtensionAdded);

    // 监听文件改变事件
    vm.on('PROJECT_CHANGED', handleProjectChanged);
  }, [vm]);

  // 移除事件
  const detachVM = useCallback(() => {
    vm.removeListener('workspaceUpdate', onWorkspaceUpdate);
    vm.removeListener('EXTENSION_ADDED', handleExtensionAdded);

    workspace.dispose();
  }, [vm, workspace, onWorkspaceUpdate, handleExtensionAdded]);

  useEffect(() => {
    loadDidMount();
  }, [skillJson]);

  useEffect(() => {
    if (markBlocksMap && loadScene) {
      loadSceneJson();
    }
  }, [markBlocksMap, loadScene]);

  const loadSceneJson = async () => {
    // 执行函数
    const { sceneUuid, modelCode, seriesCode } = location.query;
    if (id) {
      dispatch({
        type: 'editGlobal/setLoading',
        payload: {
          apiName: 'getSceneJson',
          loading: true,
        },
      });
      const { sceneJson, statusCode, statusMessage } = await dispatch({
        type: 'editGlobal/getSceneJson',
        payload: {
          sceneUuid,
          modelCode,
          seriesCode,
        },
      });

      if (statusCode === '0') {
        let { blocks } = sceneJson;

        // 处理数据
        const dateCustomList = (markBlocksMap as Map<string, number[]>).get(ElementDype.DATE_CUSTOM) || [];
        
        // 判断存不存在 date_custom 场景数据
        if (checkPlaceholdersAndParams(JSON.parse(blocks), dateCustomList)) {
          // 处理数据 传入json 传入
          // log.warn('存在date_custom的场景数据');
          blocks = JSON.stringify(replaceDateCustomData(JSON.parse(blocks), skillJson, dateCustomList));
        }

        await loadDefaultProject(JSON.parse(blocks));

        setTimeout(() => {
          dispatch({
            type: 'editGlobal/setLoading',
            payload: {
              apiName: 'getSceneJson',
              loading: false,
            },
          });
        }, 300);
      } else {
        message.warning(statusMessage);
      }
      return;
    }

    setTimeout(() => {
      dispatch({
        type: 'editGlobal/setLoading',
        payload: {
          apiName: 'getSceneJson',
          loading: false,
        },
      });
    }, 300);
  };

  const loadDidMount = async () => {
    ScratchBlocks = VMScratchBlocks(vm);

    if (!workspace) {
      const toolbox = makeToolboxXML(false, true, null, [], '', '', '');
      const workspaceConfig = Object.assign({}, BLOCKS_DEFAULT_OPTIONS, {
        toolbox: toolbox,
      });

      ScratchBlocks.ScratchMsgs.setLocale('zh-cn');

      workspace = ScratchBlocks.inject('blocks', workspaceConfig);
    }

    const extensions = getExtensions();

    setTimeout(() => {
      window.dispatchEvent(new Event('resize'));
    }, 300);

    if (!extensions.length) {
      return;
    }

    vm.runtime.attachCustomKey('abilityList', skillJson);
    // 注册扩展
    vm.extensionManager.addBuiltinExtensions(extensions);
    await extensions.forEach((item) => automaticImportComponent(item));

    await loadDefaultProject(template).then(async () => {
      setLoadScene(true);
    });
  };

  useMount(() => {
    // 加载默认项目
    loadDefaultProject(template);
    attachVM();
  });

  useUnmount(() => {
    detachVM();
    workspace = null;
  });

  return (
    <>
      {/* 组合组件 */}
      <CompositeComponent {...compositeState} />

      <div ref={blockRef} id="blocks" style={{ flex: 1 }} />
    </>
  );
};

export default connect(mapStateToProps)(Blocks);
